﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Google.Protobuf;

public enum ERunStatus
{
    None,
    Connected,
    ReadyReceive,
}

class Client
{
    UdpClient m_Client = new UdpClient();
    MessageDispatcher m_Dispatcher = new MessageDispatcher();
    ERunStatus m_Status;
    byte[] m_SendBuffer = new byte[1024];
    byte[] m_RecvBuffer;
    MemoryStream m_SendStream;
    MemoryStream m_RecvStream;
    Queue<byte[]> m_WaitSendMgs = new Queue<byte[]>();
    UniqueIDGenerator m_Generator = new UniqueIDGenerator();
    uint m_UserID;
    // pos, time
    List<KeyValuePair<float, float>> m_RecentPosXs = new List<KeyValuePair<float, float>>();

    public void Start()
    {
        MessageManager.Instance.Init(); 

        m_RecvBuffer = new byte[1024];
        m_RecvStream = new MemoryStream(m_RecvBuffer);

        m_Client.Connect("127.0.0.1", 1234);
        m_Dispatcher.Register<Hello>(EMessageType.HELLO, (msg) =>
        {
            m_UserID = msg.UserId; 
            m_Status = ERunStatus.Connected;
        });
        m_Dispatcher.Register<S2C_PlayerSync>(EMessageType.S2C_SYNC, (msg) =>
        {
            string s = "m_UserID=" + m_UserID + ", S2C_PlayerSync="; 
            foreach (var item in msg.Players)
            {
                s += "userId=" + item.UserId + ", pos=" + item.PosX;
                if (item.UserId == m_UserID)
                {
                    var dest = m_RecentPosXs.Find((temp) => temp.Key == item.PosX);
                    if (dest.Key != 0)
                    {
                        LogManager.Log("m_UserID=" + dest.Key + ", receive posx " + ((MessageManager.Instance.GetCurElapsedTime() - dest.Value) * 1000));
                    }
                }
            }
            LogManager.Log(s);
        });
        Task.Run(RecvThread);
        Task.Run(SendThread);

        m_SendStream = new MemoryStream(m_SendBuffer);
        Send(MessageManager.Instance.Get<Hello>());
        // main thread
        while (true)
        {
            if (m_Status == ERunStatus.Connected)
            {
                m_Status = ERunStatus.ReadyReceive;
            }
            if (m_Status == ERunStatus.ReadyReceive)
            {
                var msg = MessageManager.Instance.Get<C2S_PlayerSync>();
                msg.PosX = m_Generator.GetUniqueID();
                LogManager.Log("send pos=" + msg.PosX);
                if (m_RecentPosXs.Count > 5)
                {
                    m_RecentPosXs.RemoveAt(0);
                }
                m_RecentPosXs.Add(new KeyValuePair<float, float>(msg.PosX, MessageManager.Instance.GetCurElapsedTime()));
                Send(msg);
            }
            Thread.Sleep(33 * 3);
        }
    }

    public void RecvThread()
    {
        IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
        while (true)
        {
            var bs = m_Client.Receive(ref ep);
            ProcessMessage(bs);
            Thread.Sleep(1);
        }
    }
    public void SendThread()
    {
        while (true)
        {
            while (m_WaitSendMgs.Count > 0)
            {
                var m = m_WaitSendMgs.Dequeue();
                m_Client.Send(m, m.Length);
            }
            Thread.Sleep(1);
        }
    }

    public void Send(BaseMessage msg)
    {
        m_SendStream.Position = 0;
        m_SendStream.Write(BitConverter.GetBytes((ushort)MessageManager.Instance.GetMessageType(msg)));
        msg.WriteTo(m_SendStream);
        LogManager.Log("send count=" + m_SendStream.Position);
        byte[] bs = new byte[m_SendStream.Position];
        Array.Copy(m_SendBuffer, 0, bs, 0, bs.Length);
        m_WaitSendMgs.Enqueue(bs);
    }

    public void ProcessMessage(byte[] bs)
    {
        if (bs.Length < 2)
        {
            return;
        }

        var cmd = (EMessageType)BitConverter.ToUInt16(bs, 0);
        //m_RecvStream.Position = 0;
        //m_RecvStream.Write(bs, 2, bs.Length - 2);
        var type = MessageManager.Instance.GetMessageSystemType(cmd);
        var m = MessageManager.Instance.Get(type);
        byte[] bs1 = new byte[bs.Length - 2];
        Array.Copy(bs, 2, bs1, 0, bs1.Length);
        m.MergeFrom(bs1);
        m_Dispatcher.Invoke(cmd, m);
    }
}
